package com.djahva.image;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.djahva.image.ImageRequest;

import java.io.*;
import java.util.ArrayList;
import java.awt.image.BufferedImage;

/*import java.util.regex.Pattern;
  import java.util.regex.Matcher;
  eventueel ip matchen*/

/*
TODO: andere interface??
* crop & resize = true
* intellicrop
*/

/**
 * Deze servlet laat toe om op images een bepaalde bewerking te doen. Deze kunnen afkomstig zijn van
 * zowel lokale bestanden als van een URL.
 * <p/>
 * <p/>
 * <p>Een aantal init-parameters kunnen meegegeven worden in de web.xml:
 * <ul>
 * <li> <i>debug</i>: zet dit op <b>true</b> of <b>false</b>, naargelang er debug messages zijn gewenst.  </li>
 * <li> <i>sourceURL</i>: gebruik deze url als base in plaats van een lokale directory</li>
 * <li> <i>tempDir</i>: definieer deze parameters als u een niet standaard temporary directory wil gebruiken</li>
 * <li> <i>defaultFormat</i>: de extensie van het standaard formaat, dat gebruikt wordt als niets wordt opgegeven</li>
 * <li> <i>disableCaching</i>: waardes zijn <b>true</b> of <b>false</b>. Hiermee kan u caching van de images afzetten. Let wel:
 * dit kan serieuze performantieproblemen met zich meebrengen!!</li>
 * </ul>
 * </p>
 * <p/>
 * <p/>
 * De servlet zelf neemt een aantal parameters aan, waarvan het gebruik als volgt:
 * getallen kunnen zowel als absoluut getal of als procenten (achtervoegsel '<b>proc</b>') worden meegegeven. De interpretatie van de getallen
 * is afhankelijk van  de parameter, maar meestal is deze uitgedrukt in pixels.
 * <ul>
 * <li> <i>source</i>: deze parameter heeft dezelfde betekenis als de <i>sourceURL</i> in de web.xml (zie boven), maar laat toe
 * om deze op een dynamische manier te specifieren. Let wel, als deze parameter wordt gedefinieerd, wordt het lokale pad
 * niet gebruikt.</li>
 * <li> <i>intellicrop</i>: dit is een van de belangrijkste parameters aangezien ze invloed heeft op een aantal andere
 * parameters. Er is geen waarde vereist.
 * <li> <i>width</i>: specifieer de breedte van de afbeelding. Als enkel deze parameter is meegegeven zonder hoogte of
 * <i>keepaspect</i>, wordt de oorspronkelijke hoogte gebruik en wordt de afbeelding samengedrukt of uitgerekt
 * (afhankelijk van de originele breedte).</li>
 * <li> <i>height</i>: de hoogte van de afbeelding, hier geldt hetzelfde als bij breedte.</li>
 * <li> <i>keepaspect</i>: Zet dit aan als u wil zorgen dat bij een resize de aspectratio wordt bewaard. Dit werkt enkel
 * als u <i>width</i> OF <i>height</i> specifieert.</li>
 * <li> <i>colordepth</i>: de kleurdiepte van de image. Als niets is opgegeven, wordt de originele kleurdiepte
 * verondersteld. Mogelijke waarden zijn <b>gray</b>, voor grijswaarden of <b>bw</b> voor zwart-wit.</li>
 * <li> <i>crx</i>: De x-waarde vanwaar de crop moet beginnen. Bij de intellicrop heeft deze de betekenis van MIDDELPUNT
 * x-waarde voor de crop. Als deze niet is gespecifieerd bij de intelligecrop, wordt automatisch het middenpunt
 * van de afbeelding gebruikt.</li>
 * <li> <i>cry</i>: De y-waarde vanwaar de crop begint. Ook hier weer gelden dezelfde regels als bij <i>crx</i>.</li>
 * <li> <i>crheight</i>: De hoogte waarnaar gecropt wordt. Bij de intellicrop wordt dit geinterpreteerd als zowel hoogte
 * voor de crop als voor de resize.</li>
 * <li> <i>crwidth</i>: De breedte voor de crop. Dezelfde regel als bij <i>crheight</i></li>
 * <li> <i>flip</i>: Spiegelen wordt geregeld met deze parameters, die als mogelijke waarden <b>h</b> of <b>v</b> voor
 * respectievelijk horizontaal en verticaal is. Beide spiegelingen worden uitgevoerd met de waarde <b>d</b> (diagonaal).
 * </li>
 * <p/>
 * <li> <i>rotate</i>: Het roteren van de afbeelding wordt hiermee bepaald. Parameter is het aantal graden, of procenten
 * van de goniometrische cirkel.</li>
 * <p/>
 * <li> <i>overlayalpha</i>: De alpha die gebruikt wordt voor het overlayimage. Deze bepaalt hoe zichtbaar de
 * achterliggende afbeelding blijft. Mogelijke waardes van <b>0</b> tot en met <b>1</b> of in procenten van <b>0proc</b>
 * tot <b>100proc</b>.</li>
 * <li> <i>overlay</i>: De url van het overlayimage. Deze kan terug naar de servlet verwijzen, maar meer dan
 * een overlay is niet mogelijk.</li>
 * <p/>
 * <li> <i>compression</i>: De compressie van de uiteindelijke afbeelding wordt hiermee bepaald, maar dit geldt
 * enkel voor formaten (zie <i>format</i> hieronder) die compressie toelaten. Toegelaten waarde is een getal tussen
 * <b>0</b> en <b>1</b> of een procent dat hiermee overeenkomt.</li>
 * <li> <i>format</i>: Het formaat waarin de afbeelding wordt afgeleverd, bepaald met de normale extensie. Mogelijke
 * waardes zijn <b>jpg</b> en <b>png</b> voor JDK 1.4. JDK 1.5 heeft meer formaten beschikbaar: ...</li>
 * <p/>
 * <li> <i>targetsize</i>: De uiteindelijke grootte die moet bereikt worden. Dit kan enkel met een compresseerbaar
 * formaat (zie <i>format</i>), bv jpg. De grootte is meegegeven in bytes en laat geen procentuele bepalingen toe.
 * Extreme waarden worden nooit bereikt!</li>
 * <p/>
 * </ul>
 *
 * @see javax.imageio.ImageIO
 */

/* todo Meer info kunnen opvragen per request
 * todo file upload??? (default disabled)
 * todo html form met params.
 * todo check allowed formats, choose best ;-)*/

public class ImageServlet extends HttpServlet{
    //private String defaultFormat = "jpg";
	private static final int FILEBUFFER = 10240;
	private ImageCache imageCache;
	private int totalThroughput = 0;
	private int totalComputationTime = 0;
	private int maxImageSize;
	private String allowedIPs;
	private String allowedRole;

	public void init(ServletConfig cfg) throws ServletException {
		super.init(cfg);
        //System.out.println("ImageServlet init with location directory "+root);
        System.setProperty("java.awt.headless","true");
        
        int cacheSize;
        int cacheAge;
        String cacheFolder;
        if(getInitParameter("cacheSize") != null){
        	cacheSize = Integer.parseInt(getInitParameter("cacheSize").trim()) * 1024 * 1024; //MB -> B
        } else {
        	cacheSize = 20 * 1024 * 1024;
        }
        
        if(getInitParameter("cacheAge") != null){
        	cacheAge = Integer.parseInt(getInitParameter("cacheAge").trim()) * 60000; // min -> millisec
        } else {
        	cacheAge = 120000;
        }
        
        if(getInitParameter("cacheFolder") != null){
        	cacheFolder = getInitParameter("cacheFolder").trim();
        } else {
        	cacheFolder = "cache";
        }
        imageCache = new ImageCache(cacheFolder,cacheSize, cacheAge);
        
        if(getInitParameter("cacheSize") != null){
        	maxImageSize = Integer.parseInt(getInitParameter("cacheSize").trim()) * 1024 * 1024; //MB -> B
        } else {
        	maxImageSize = 1024 * 1024;
        }
        
        if(getInitParameter("allowedIPs") != null){
        	allowedIPs = getInitParameter("allowedIPs").trim();
        } else {
        	allowedIPs = "127.0.0.1";
        }
        
        if(getInitParameter("allowedRole") != null){
        	allowedRole = getInitParameter("allowedRole");
        } else {
        	allowedRole = "";
        }
	}

	public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException	{
		
		long beginTotalComp = System.currentTimeMillis();
		
        String pathInfo = req.getPathInfo();
        
        if (pathInfo == null) pathInfo = "";

        String sourceURL = getServletContext().getRealPath( req.getServletPath());//+ pathInfo);
        ImageRequest img = new ImageRequest(sourceURL,pathInfo,maxImageSize);
        img.log("sourceURL: "+sourceURL);
        img.log("pathInfo: "+sourceURL);
        // general properties
        // find local properties
        File props = localFile(sourceURL,pathInfo,"images.properties");
        if (props.exists()){
            img.readFile(props);
        }

        props = localFile(sourceURL,pathInfo,"images.xml");
        if (props.exists()){
            img.readXMLFile(props);
        }

        img.readMap(req.getParameterMap());

        if(!adminRequest(req, res, img)){
            OutputStream os = res.getOutputStream();
            //cache checken
            byte[] bytes = imageCache.get(img.properties, img.img);
        	try {
            	if(bytes != null && req.getParameter("nocache") == null){
            	//in cache
            		//TODO check file time stamp
                	os.write(bytes);
                	totalThroughput += bytes.length;
                	os.flush();
                } else{
                //niet in cache
                	long beginProcesTime = System.currentTimeMillis();
	                BufferedImage bi = img.getImage();
	                if (bi != null) {
	                    String mime = img.getMime();
	                    if (mime == null) mime = "image/jpeg";
	                    res.setContentType(mime);
	
	                    img.writeTo(os);
	                    os.flush();
	                    os.close();
	                    
	                    long endProcesTime = System.currentTimeMillis();
	                    // image opslaan in cache (behalve als user niet wil)
	                    if (req.getParameter("nocache") == null){
	                    	totalThroughput += imageCache.store(img.image, mime, img.properties, img.img, endProcesTime-beginProcesTime);
	                    }
	                } else {
	                    res.sendError(HttpServletResponse.SC_NOT_FOUND, " NOT FOUND! "+sourceURL );
	                }
                }
            } catch (IOException e) {
                res.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, " NOT FOUND! " + e);
            } catch(IllegalArgumentException e){
            	res.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE,e.getMessage());
            } catch (RuntimeException e ){
                System.out.println("error "+e);
                e.printStackTrace();
                res.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, " NOT FOUND! " );
            }
        }
        long endTotalComp = System.currentTimeMillis();
        totalComputationTime += (endTotalComp - beginTotalComp);
	}

	private boolean adminRequest(HttpServletRequest req, HttpServletResponse res,
			ImageRequest img) throws IOException {
		String remoteIP = req.getRemoteAddr();
		boolean userAllowed = req.isUserInRole(allowedRole);
		if(allowedIPs.contains(remoteIP) || userAllowed || allowedIPs.contains("*")){
			if(req.getParameter("flush") != null){
				imageCache.cleanUpByTime();
			} else if(req.getParameter("clear") != null){
				imageCache.cleanUp();
			}
	      
			if (req.getParameter("info") != null){
			    PrintWriter out = res.getWriter();
			    res.setContentType("text/html");
			    try{
			        BufferedImage bi = img.getImage();
			    } catch (Exception e){
			        out.print("<div><pre>");
			        out.print(e.getMessage());
			        out.print("</pre></div>");
			    }
			    img.properties.remove("info");
			    byte[] bytes = imageCache.get(img.properties, img.img);
			    if(bytes != null){
			    	img.log("Image found in cache");
			    } else {
			    	img.log("Image not found in cache");
			    }
			    out.print(img.getLog());
			    return true;
			}
			if (req.getParameter("cache") != null){
				PrintWriter out = res.getWriter();
			    res.setContentType("text/html");
			    out.print("<h1>Servlet info</h1>");
			    out.print("<h2>General info</h2>");
			    out.print("<div><p>Total computation time: " + totalComputationTime + " ms<br/>");
			    if(totalThroughput < 1024*1024){
			    	out.print("Total throughput: " + String.format("%10.3f", (double)totalThroughput/1024) + " KB </p></div>");
			    } else {
			    	out.print("Total throughput: " + String.format("%10.3f", (double)totalThroughput/(1024*1024)) + " MB </p></div>");
			    }
			    out.print("<h2>Cache info</h2>");
			    out.print(imageCache.info());
			    return true;
			}
		}
		return false;
	}

    private File localFile(String sourceURL, String pathInfo,String fileName) {
        File f = new File(sourceURL,pathInfo);
        File props = new File(f.getParentFile(),fileName);
        return props;
    }

    private void copyStreamToStream(InputStream is, OutputStream os) throws IOException	{
		byte[] b = new byte[FILEBUFFER];
		int bytes = is.read(b);
		while (bytes != -1)
		{
			os.write(b, 0, bytes);
			bytes = is.read(b);
		}
		is.close();
	}
}